package oktx.narayana;

import com.arjuna.ats.internal.jdbc.BaseTransactionalDriverXAConnection;
import com.arjuna.ats.internal.jdbc.ConnectionManager;
import com.arjuna.ats.jdbc.TransactionalDriver;

import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

/**
 * Created by huangdachao on 2018/6/5 17:07.
 */
@SuppressWarnings("unchecked")
public class NarayanaConnectionManager extends ConnectionManager {
    private static Set<NarayanaConnection> _connections;

    static {
        try {
            Field f = ConnectionManager.class.getDeclaredField("_connections");
            f.setAccessible(true);
            _connections = (Set<NarayanaConnection>) f.get(null);
        } catch (NoSuchFieldException | SecurityException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    /*
     * Connections are pooled for the duration of a transaction.
     */
    public static synchronized Connection create(String dbUrl, Properties info) throws SQLException {
        String user = info.getProperty(TransactionalDriver.userName, "");
        String passwd = info.getProperty(TransactionalDriver.password, "");
        String dynamic = info.getProperty(TransactionalDriver.dynamicClass, "");
        String poolConnections = info.getProperty(TransactionalDriver.poolConnections, "true");
        Object xaDataSource = info.get(TransactionalDriver.XADataSource);
        int maxConnections = Integer.valueOf(info.getProperty(TransactionalDriver.maxConnections, "100"));

        if (dbUrl == null) {
            dbUrl = "";
        }

        boolean poolingEnabled = "true".equalsIgnoreCase(poolConnections);
        NarayanaConnection conn = null;
        synchronized (_connections) {
            if (poolingEnabled) {
                Iterator<NarayanaConnection> iterator = _connections.iterator();
                while (iterator.hasNext()) {
                    NarayanaConnection c = iterator.next();
                    BaseTransactionalDriverXAConnection connControl = c.getConnectionControl();
                    TransactionManager tm = com.arjuna.ats.jta.TransactionManager.transactionManager();
                    Transaction tx1 = connControl.transaction();
                    Transaction tx2 = null;

                    try {
                        tx2 = tm.getTransaction();
                    } catch (javax.transaction.SystemException se) {
                        /* Ignore: tx2 is null already */
                    }

                    /* Check transaction and database connection. */
                    if ((tx1 != null && tx1.equals(tx2))
                        && isSameConnection(dbUrl, user, passwd, dynamic, xaDataSource, connControl)) {
                        try {
                            /*
                             * Should not overload the meaning of closed. Change!
                             */
                            if (!c.isClosed()) {
                                // ConnectionImple does not actually implement Connection, but its
                                // concrete child classes do. See ConnectionImple javadoc.
                                synchronized (c) { // 使用同步代码块，防止在incrementUseCount之间被关闭连接
                                    if (!c.isClosed()) {
                                        conn = c;
                                        conn.incrementUseCount();
                                        break;
                                    }
                                }
                            }
                        } catch (Exception ex) {
                            ex.printStackTrace();
                            SQLException sqlException = new SQLException(ex.getMessage());
                            sqlException.initCause(ex);
                            throw sqlException;
                        }
                    } else {
                        // no longer being used by a transaction, so let's discard. JBTM-764
                        //
                        // TODO
                        //
                        //                    if (tx1 == null)
                        //                        iterator.remove();
                    }
                }

                while (conn == null) {
                    for (NarayanaConnection con : _connections) {
                        if (!con.inUse() && isSameConnection(dbUrl, user, passwd, dynamic, xaDataSource, con.getConnectionControl())) {
                            synchronized (con) { // 使用同步代码块，防止在incrementUseCount之间被关闭连接
                                if (!con.inUse()) {
                                    conn = con;
                                    conn.incrementUseCount();
                                    break;
                                }
                            }
                        }
                    }
                    if (conn == null) {
                        if (_connections.size() == maxConnections) {
                            try {
                                _connections.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        } else {
                            break;
                        }
                    }
                }
            }


            if (conn == null) {
                conn = new NarayanaConnection(dbUrl, info);
                /*
                 * Will replace any old (closed) connection which had the
                 * same connection information.
                 */

                if (poolingEnabled) {
                    _connections.add(conn);
                }
            }

            // ConnectionImple does not actually implement Connection, but its
            // concrete child classes do. See ConnectionImple javadoc.
            return conn;
        }
    }

    public static Set<NarayanaConnection> getConnections() {
        return Collections.unmodifiableSet(_connections);
    }

    private static boolean isSameConnection(String dbUrl, String user, String passwd, String dynamic, Object xaDataSource, BaseTransactionalDriverXAConnection connControl) {
        return
            dbUrl.equals(connControl.url())
                && user.equals(connControl.user())
                && passwd.equals(connControl.password())
                && dynamic.equals(connControl.dynamicClass())
                // equal ProvidedXADataSourceConnection instances should have the same data source
                && (xaDataSource == null || xaDataSource.equals(connControl.xaDataSource()));
    }
}
